//===----------------------------------------------------------------------===//
//
//                         Peloton
//
// group.h
//
// Identification: src/include/optimizer/group.h
//
// Copyright (c) 2015-16, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#pragma once

#include <unordered_map>
#include <vector>

#include "optimizer/group_expression.h"
#include "optimizer/operator_node.h"
#include "optimizer/property.h"
#include "optimizer/property_set.h"
#include "optimizer/stats/column_stats.h"

namespace peloton {
namespace optimizer {

using GroupID = int32_t;

const GroupID UNDEFINED_GROUP = -1;
class ColumnStats;

//===--------------------------------------------------------------------===//
// Group
//===--------------------------------------------------------------------===//
class Group : public Printable {
 public:
  Group(GroupID id, std::unordered_set<std::string> table_alias);

  // If the GroupExpression is generated by applying a
  // property enforcer, we add them to enforced_exprs_
  // which will not be enumerated during OptimizeExpression
  void AddExpression(std::shared_ptr<GroupExpression> expr, bool enforced);

  void RemoveLogicalExpression(size_t idx) {
    logical_expressions_.erase(logical_expressions_.begin() + idx);
  }

  bool SetExpressionCost(GroupExpression *expr, double cost,
                         std::shared_ptr<PropertySet> &properties);

  GroupExpression *GetBestExpression(std::shared_ptr<PropertySet> &properties);

  inline const std::unordered_set<std::string> &GetTableAliases() const {
    return table_aliases_;
  }

  // TODO: thread safety?
  const std::vector<std::shared_ptr<GroupExpression>> GetLogicalExpressions()
      const {
    return logical_expressions_;
  }

  // TODO: thread safety?
  const std::vector<std::shared_ptr<GroupExpression>> GetPhysicalExpressions()
      const {
    return physical_expressions_;
  }

  inline double GetCostLB() { return cost_lower_bound_; }

  inline void SetExplorationFlag() { has_explored_ = true; }
  inline bool HasExplored() { return has_explored_; }

  std::shared_ptr<ColumnStats> GetStats(std::string column_name);

  void AddStats(std::string column_name, std::shared_ptr<ColumnStats> stats);

  bool HasColumnStats(std::string column_name);

  /*
   * HasExpressions - Determines whether or not a lowest cost expression exists
   * for this group
   *
   * properties: the property set used to search the lowest cost expressions
   * return: true if an expression is found, false otherwise
   */
  bool HasExpressions(const std::shared_ptr<PropertySet> &properties) const;

  void SetNumRows(size_t num_rows) { num_rows_ = num_rows; }

  int GetNumRows() { return num_rows_; }

  inline GroupID GetID() const { return id_; }

  const std::string GetInfo(int num_indent) const;
  const std::string GetInfo() const override;

  // This is called in rewrite phase to erase the only logical expression in the
  // group
  inline void EraseLogicalExpression() {
    PELOTON_ASSERT(logical_expressions_.size() == 1);
    PELOTON_ASSERT(physical_expressions_.size() == 0);
    logical_expressions_.clear();
  }

  // This should only be called in rewrite phase to retrieve the only logical
  // expr in the group
  inline GroupExpression *GetLogicalExpression() {
    PELOTON_ASSERT(logical_expressions_.size() == 1);
    PELOTON_ASSERT(physical_expressions_.size() == 0);
    return logical_expressions_[0].get();
  }

 private:
  GroupID id_;
  // All the table alias this group represents. This will not change once create
  // TODO(boweic) Do not use string, store table alias id
  std::unordered_set<std::string> table_aliases_;
  std::unordered_map<std::shared_ptr<PropertySet>,
                     std::tuple<double, GroupExpression *>, PropSetPtrHash,
                     PropSetPtrEq> lowest_cost_expressions_;

  // Whether equivalent logical expressions have been explored for this group
  bool has_explored_;

  std::vector<std::shared_ptr<GroupExpression>> logical_expressions_;
  std::vector<std::shared_ptr<GroupExpression>> physical_expressions_;
  std::vector<std::shared_ptr<GroupExpression>> enforced_exprs_;

  // We'll add stats lazily
  // TODO(boweic):
  // 1. use table alias id + column offset to identify the column
  // 2. Support stats for arbitary expressions
  std::unordered_map<std::string, std::shared_ptr<ColumnStats>> stats_;
  int num_rows_ = -1;
  double cost_lower_bound_ = -1;
};

}  // namespace optimizer
}  // namespace peloton
